You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
87 lines
2.7 KiB
87 lines
2.7 KiB
import multer from "multer";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { callNodeListener, getRequestIP } from "h3";
|
|
import { POST_MEDIA_PUBLIC_PREFIX } from "#server/constants/media";
|
|
import { replaceMediaAssetFileFromTempUpload } from "#server/service/media";
|
|
import { requireAdmin } from "#server/utils/admin-guard";
|
|
import { R } from "#server/utils/response";
|
|
import { assertUnderRateLimit } from "#server/utils/simple-rate-limit";
|
|
|
|
type MulterUploadedFile = {
|
|
originalname: string;
|
|
filename: string;
|
|
path: string;
|
|
mimetype: string;
|
|
size: number;
|
|
};
|
|
|
|
export default defineWrappedResponseHandler(async (event) => {
|
|
const admin = await requireAdmin(event);
|
|
const ip = getRequestIP(event, { xForwardedFor: true }) ?? "unknown";
|
|
assertUnderRateLimit(`admin-media-asset-reupload:${ip}`, 40, 60_000);
|
|
|
|
const idRaw = getRouterParam(event, "id");
|
|
const assetId = Number(idRaw);
|
|
if (!Number.isInteger(assetId) || assetId < 1) {
|
|
throw createError({ statusCode: 400, statusMessage: "无效的资源 id" });
|
|
}
|
|
|
|
const upload = multer({
|
|
storage: multer.diskStorage({
|
|
destination: (_req, _file, cb) => {
|
|
cb(null, os.tmpdir());
|
|
},
|
|
filename: (_req, file, cb) => {
|
|
const ext = path.extname(file.originalname).toLowerCase();
|
|
const baseName = path.basename(file.originalname, ext).replace(/[^a-z0-9]/gi, "-");
|
|
cb(null, `reupload-${Date.now()}-${Math.round(Math.random() * 1e9)}-${baseName}${ext}`);
|
|
},
|
|
}),
|
|
limits: { fileSize: 10 * 1024 * 1024 },
|
|
fileFilter: (_req, file, cb) => {
|
|
const allowed = ["image/png", "image/jpeg", "image/jpg", "image/webp"];
|
|
if (allowed.includes(file.mimetype)) {
|
|
cb(null, true);
|
|
} else {
|
|
cb(new Error("只支持 PNG/JPG/WebP 格式图片"));
|
|
}
|
|
},
|
|
});
|
|
|
|
await callNodeListener(
|
|
// @ts-expect-error multer 中间件类型
|
|
upload.single("file"),
|
|
event.node.req,
|
|
event.node.res,
|
|
);
|
|
|
|
// @ts-expect-error multer 挂载 file
|
|
const file = event.node.req.file as MulterUploadedFile | undefined;
|
|
if (!file?.path) {
|
|
throw createError({ statusCode: 400, statusMessage: "请选择要上传的图片" });
|
|
}
|
|
|
|
try {
|
|
const result = await replaceMediaAssetFileFromTempUpload({
|
|
assetId,
|
|
actorUserId: admin.id,
|
|
actorIsAdmin: true,
|
|
tempInputPath: file.path,
|
|
});
|
|
return R.success({
|
|
url: `${POST_MEDIA_PUBLIC_PREFIX}${result.storageKey}`,
|
|
sizeBytes: result.sizeBytes,
|
|
});
|
|
} catch (err) {
|
|
if (file.path && fs.existsSync(file.path)) {
|
|
try {
|
|
fs.unlinkSync(file.path);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
throw err;
|
|
}
|
|
});
|
|
|